#include "BaseErrorInfo.h"
#include "SystemErrorInfo.h"
#include "TaisanErrorInfo.h"
#include "MysqlErrorInfo.h"

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <getopt.h>
#include <assert.h>
#include <string.h>
#include <string>
#include <vector>

#define PROG_NAME "terror"

static void print_usage( void );
static void print_nofound( int code );
static void print_error( const char *type, const struct error_info_t &info );

static int not_digit( const char *str ); // reutnr value: 1 not digit, 0 is digit
static int query_errno( const char *ename );

static const struct option long_options[] = {
	{ "sys", 0, 0, 's' },
	{ "taisan", 0, 0, 't' },
	{ "mysql", 0, 0, 'm' },
	{ "all", 0, 0, 'a' },
	{ "error-code", 0, 0, 'c' },
	{ "help", 0, 0, 'h' },
	{ "version", 0, 0, 'v' },
	{ 0, 0, 0, 0 }
};


#define PRINT_NO_FOUND_STR( x ) fprintf( stderr, "Can't get [%s]'s error code\n", (x) )

int main( int argc, char *argv[] )
{
	// get option
	bool q_sys_error = false;
	bool q_taisan_error = false;
	bool q_mysql_error = false;
	bool q_all_error = true;
	bool only_get_code = false;

	const char *short_opts = "stmachv";
	int opt;

	while( (opt = getopt_long( argc, argv, short_opts, long_options, NULL )) != -1 ) {
		switch( opt ) {
		case 's':
			q_all_error = false; q_sys_error = true;
			break;
		case 't':
			q_all_error = false; q_taisan_error = true;
			break;
		case 'm':
			q_all_error = false; q_mysql_error = true;
			break;
		case 'a':
			q_all_error = true;
			break;
		case 'c':
			only_get_code = true;
			break;
		case 'v':
		case 'h':
		default:
			print_usage();
			exit( EXIT_FAILURE );
		}
	}

	if( optind == argc ) {
		print_usage();
		exit( EXIT_FAILURE );
	}

	// init all error codes
	int exit_value = 0;
	std::vector< int > codes;
	for( int i = optind; i < argc; i++ ) {
		int code;
		if( not_digit( argv[i] ) ) {
			code = query_errno( argv[i] );
			if( -1 == code ) {
				PRINT_NO_FOUND_STR( argv[i] );
				exit_value = 1;
			}
			else {
				codes.push_back( code );
			}
			if( only_get_code ) {
				printf( "Found error code %s : %d\n", argv[i], code );
			}
			continue;
		}
		else {
			code = atoi( argv[i] );
		}
		codes.push_back( code );
	}

	if( only_get_code ) {
		exit( exit_value );
	}

	std::vector < BaseErrorInfo * > errors;
	if( q_all_error || q_sys_error ) errors.push_back( new SystemErrorInfo );
	if( q_all_error || q_taisan_error ) errors.push_back( new TaisanErrorInfo );
	if( q_all_error || q_mysql_error ) errors.push_back( new MysqlErrorInfo );

	for( size_t i = 0; i < codes.size(); i++ ) {
		int code = codes.at( i );
		bool found = false;

		struct error_info_t info;
		std::vector < BaseErrorInfo * >::const_iterator iter = errors.begin();
		for(; iter != errors.end(); iter++ ) {
			info.Reset();
			if( (*iter)->FindError( code, info ) == 0 ) {
				found = true;
				print_error( (*iter)->GetErrorType(), info );
			}
		}
		if( !found ) {
			exit_value = 1;
			print_nofound( code );
		}
	}

	for( size_t i = 0; i < errors.size(); i++ )
		delete errors.at( i );

	exit( exit_value );
}

void print_usage( void )
{
	printf( PROG_NAME " [OPTIONS] ERRORCODE or ERRORCODE_NAME [ERRORCODE...]\n" );
	printf( "\nOPTION :\n%-14s %-7s %-7s %-7s\n", "name", "has_arg", "flag", "val" );
	for( const struct option *opt = long_options; NULL != opt->name; opt++ )
		printf( "%-14s %-7d %-7d %-7c\n", opt->name, opt->has_arg, (int)opt->flag, opt->val );
	printf( "\nEXAMPLE :\n" PROG_NAME " EAGAIN 1014 10\n" );
}

void print_nofound( int code )
{
	fprintf( stderr, "Illegal error code: %d\n", code );
}

void print_error( const char *type, const struct error_info_t &info )
{
	printf( "%s %s %d: %s\n", type, NULL == info.group ? "" : info.group,
			info.code, info.info.c_str() );
}

int not_digit( const char *str )
{
	assert( NULL != str );
	while( '\0' != *str ) {
		if( !isdigit( *str ) )
			return 1;
		str++;
	}

	return 0;
}

int query_errno( const char *ename )
{
	// this code define is from /usr/include/asm-generic/errno.h and /usr/include/asm-generic/errno-base.h
	struct key_value {
		const char *key;
		int value;
	};
	struct key_value key_values[] = {
		{ "EPERM", 1 },
		{ "ENOENT", 2 },
		{ "ESRCH", 3 },
		{ "EINTR", 4 },
		{ "EIO", 5 },
		{ "ENXIO", 6 },
		{ "E2BIG", 7 },
		{ "ENOEXEC", 8 },
		{ "EBADF", 9 },
		{ "ECHILD", 10 },
		{ "EAGAIN", 11 },
		{ "ENOMEM", 12 },
		{ "EACCES", 13 },
		{ "EFAULT", 14 },
		{ "ENOTBLK", 15 },
		{ "EBUSY", 16 },
		{ "EEXIST", 17 },
		{ "EXDEV", 18 },
		{ "ENODEV", 19 },
		{ "ENOTDIR", 20 },
		{ "EISDIR", 21 },
		{ "EINVAL", 22 },
		{ "ENFILE", 23 },
		{ "EMFILE", 24 },
		{ "ENOTTY", 25 },
		{ "ETXTBSY", 26 },
		{ "EFBIG", 27 },
		{ "ENOSPC", 28 },
		{ "ESPIPE", 29 },
		{ "EROFS", 30 },
		{ "EMLINK", 31 },
		{ "EPIPE", 32 },
		{ "EDOM", 33 },
		{ "ERANGE", 34 },
		{ "EDEADLK", 35 },
		{ "ENAMETOOLONG", 36 },
		{ "ENOLCK", 37 },
		{ "ENOSYS", 38 },
		{ "ENOTEMPTY", 39 },
		{ "ELOOP", 40 },
		{ "EWOULDBLOCK", 11 },
		{ "ENOMSG", 42 },
		{ "EIDRM", 43 },
		{ "ECHRNG", 44 },
		{ "EL2NSYNC", 45 },
		{ "EL3HLT", 46 },
		{ "EL3RST", 47 },
		{ "ELNRNG", 48 },
		{ "EUNATCH", 49 },
		{ "ENOCSI", 50 },
		{ "EL2HLT", 51 },
		{ "EBADE", 52 },
		{ "EBADR", 53 },
		{ "EXFULL", 54 },
		{ "ENOANO", 55 },
		{ "EBADRQC", 56 },
		{ "EBADSLT", 57 },
		{ "EDEADLOCK", 35 },
		{ "EBFONT", 59 },
		{ "ENOSTR", 60 },
		{ "ENODATA", 61 },
		{ "ETIME", 62 },
		{ "ENOSR", 63 },
		{ "ENONET", 64 },
		{ "ENOPKG", 65 },
		{ "EREMOTE", 66 },
		{ "ENOLINK", 67 },
		{ "EADV", 68 },
		{ "ESRMNT", 69 },
		{ "ECOMM", 70 },
		{ "EPROTO", 71 },
		{ "EMULTIHOP", 72 },
		{ "EDOTDOT", 73 },
		{ "EBADMSG", 74 },
		{ "EOVERFLOW", 75 },
		{ "ENOTUNIQ", 76 },
		{ "EBADFD", 77 },
		{ "EREMCHG", 78 },
		{ "ELIBACC", 79 },
		{ "ELIBBAD", 80 },
		{ "ELIBSCN", 81 },
		{ "ELIBMAX", 82 },
		{ "ELIBEXEC", 83 },
		{ "EILSEQ", 84 },
		{ "ERESTART", 85 },
		{ "ESTRPIPE", 86 },
		{ "EUSERS", 87 },
		{ "ENOTSOCK", 88 },
		{ "EDESTADDRREQ", 89 },
		{ "EMSGSIZE", 90 },
		{ "EPROTOTYPE", 91 },
		{ "ENOPROTOOPT", 92 },
		{ "EPROTONOSUPPORT", 93 },
		{ "ESOCKTNOSUPPORT", 94 },
		{ "EOPNOTSUPP", 95 },
		{ "EPFNOSUPPORT", 96 },
		{ "EAFNOSUPPORT", 97 },
		{ "EADDRINUSE", 98 },
		{ "EADDRNOTAVAIL", 99 },
		{ "ENETDOWN", 100 },
		{ "ENETUNREACH", 101 },
		{ "ENETRESET", 102 },
		{ "ECONNABORTED", 103 },
		{ "ECONNRESET", 104 },
		{ "ENOBUFS", 105 },
		{ "EISCONN", 106 },
		{ "ENOTCONN", 107 },
		{ "ESHUTDOWN", 108 },
		{ "ETOOMANYREFS", 109 },
		{ "ETIMEDOUT", 110 },
		{ "ECONNREFUSED", 111 },
		{ "EHOSTDOWN", 112 },
		{ "EHOSTUNREACH", 113 },
		{ "EALREADY", 114 },
		{ "EINPROGRESS", 115 },
		{ "ESTALE", 116 },
		{ "EUCLEAN", 117 },
		{ "ENOTNAM", 118 },
		{ "ENAVAIL", 119 },
		{ "EISNAM", 120 },
		{ "EREMOTEIO", 121 },
		{ "EDQUOT", 122 },
		{ "ENOMEDIUM", 123 },
		{ "EMEDIUMTYPE", 124 },
		{ "ECANCELED", 125 },
		{ "ENOKEY", 126 },
		{ "EKEYEXPIRED", 127 },
		{ "EKEYREVOKED", 128 },
		{ "EKEYREJECTED", 129 }
	};

	for( unsigned int i = 0; i < sizeof( key_values ) / sizeof( struct key_value ); i++ ) {
		if( strcasecmp( ename, key_values[i].key ) == 0 )
			return key_values[i].value;
	}
	return -1;
}

